<?php

declare(strict_types=1);

use Core\App;

/**
 * ============================================================================
 * 核心应用函数
 * ============================================================================
 */

/**
 * 获取 App 实例的辅助函数
 *
 * @param array $config 可选的配置数组，仅在首次获取实例时有效
 * @return Core\App App 单例实例
 */
function app(array $config = []): Core\App
{
    return App::getInstance($config);
}

/**
 * 获取配置项
 *
 * @param string $key 配置键名，支持点语法（如 'database.host'）
 * @param mixed $default 默认值
 * @return mixed
 */
function config(string $key, $default = null)
{
    static $configs = [];

    // 解析点语法
    $keys = explode('.', $key);
    $file = array_shift($keys);

    // 懒加载配置文件
    if (!isset($configs[$file])) {
        $configFile = defined('CONFIG_PATH')
            ? CONFIG_PATH . $file . '.php'
            : ROOT_PATH . 'config/' . $file . '.php';

        if (!file_exists($configFile)) {
            return $default;
        }

        $configs[$file] = require $configFile;
    }

    // 获取嵌套值
    $value = $configs[$file];
    foreach ($keys as $k) {
        if (!is_array($value) || !array_key_exists($k, $value)) {
            return $default;
        }
        $value = $value[$k];
    }

    return $value;
}

/**
 * 获取数据表操作对象
 *
 * @param string $table 数据表名称
 * @param string $item 数据库配置名称
 * @return object 数据库操作对象
 */
function db(string $table, string $item = 'db'): object
{
    $conf = config($item);

    if (!$conf) {
        throw new RuntimeException("数据库配置 '{$item}' 不存在");
    }

    return \Core\Db::getInstance($conf, $table, $item);
}

/**
 * ============================================================================
 * 视图和响应函数
 * ============================================================================
 */

/**
 * 渲染视图并返回生成的内容
 *
 * @param string $view 视图模板的名称或路径
 * @param array $data 要传递给视图的数据
 * @return void
 * @throws RuntimeException 当视图配置加载失败时
 */
function view(string $view, array $data = []): void
{
    $config = config('view');

    if (!$config) {
        throw new RuntimeException('视图配置加载失败');
    }

    $template = new \think\Template($config);
    $template->fetch($view, $data);
}

/**
 * 输出 JSON 响应
 *
 * @param mixed $data 响应数据
 * @param string $message 消息内容
 * @param int $code 业务状态码（0=成功，非0=失败）
 * @param int $httpCode HTTP 状态码
 * @return void
 */
function json_response($data = null, string $message = '', int $code = 0, int $httpCode = 200): void
{
    if (headers_sent()) {
        throw new RuntimeException('HTTP headers 已发送，无法输出 JSON 响应');
    }

    header('Content-Type: application/json; charset=utf-8');
    http_response_code($httpCode);

    $response = [
        'code' => $code,
        'message' => $message,
        'data' => $data,
        'timestamp' => time(),
    ];

    echo json_encode(
        $response,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
    );

    exit;
}

/**
 * 输出成功的 JSON 响应
 *
 * @param mixed $data 响应数据
 * @param string $message 消息内容
 * @return void
 */
function json_success($data = null, string $message = '操作成功'): void
{
    json_response($data, $message, 0, 200);
}

/**
 * 输出失败的 JSON 响应
 *
 * @param string $message 错误消息
 * @param int $code 业务错误码
 * @param int $httpCode HTTP 状态码
 * @return void
 */
function json_error(string $message = '操作失败', int $code = 1, int $httpCode = 400): void
{
    json_response(null, $message, $code, $httpCode);
}

/**
 * 执行 HTTP 重定向
 *
 * @param string $url 目标 URL
 * @param int $status HTTP 状态码
 * @return void
 * @throws RuntimeException 如果 headers 已发送
 * @throws InvalidArgumentException 如果 URL 无效
 */
function redirect(string $url, int $status = 302): void
{
    if (headers_sent()) {
        throw new RuntimeException('HTTP headers 已发送，无法重定向');
    }

    // 验证 URL（允许相对路径）
    if (!preg_match('/^(https?:\/\/|\/)/i', $url)) {
        throw new InvalidArgumentException('无效的重定向 URL');
    }

    // 防止 HTTP 响应拆分攻击
    $url = str_replace(["\n", "\r"], '', $url);

    http_response_code($status);
    header('Location: ' . $url, true, $status);
    exit;
}

/**
 * 终止请求并返回错误页面
 *
 * @param int $code HTTP 状态码
 * @param string $message 自定义错误信息
 * @return void
 */
function abort(int $code, string $message = ''): void
{
    $messages = [
        400 => '请求错误',
        401 => '未授权',
        403 => '无访问权限',
        404 => '页面不存在',
        405 => '方法不允许',
        500 => '服务器内部错误',
        503 => '服务不可用',
    ];

    if (!isset($messages[$code])) {
        $code = 500;
    }

    $message = $message ?: $messages[$code];
    http_response_code($code);

    // 如果是 AJAX 请求，返回 JSON
    if (is_ajax()) {
        json_error($message, $code, $code);
    }

    // 渲染错误页面
    $errorTemplate = defined('CORE_PATH')
        ? CORE_PATH . 'templates/abort.php'
        : __DIR__ . '/templates/abort.php';

    if (file_exists($errorTemplate)) {
        require $errorTemplate;
    } else {
        echo "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>错误 {$code}</title></head>";
        echo "<body><h1>错误 {$code}</h1><p>{$message}</p></body></html>";
    }

    exit(1);
}

/**
 * ============================================================================
 * 请求处理函数
 * ============================================================================
 */

/**
 * 获取请求输入数据
 *
 * @param string|null $key 字段名，null 返回所有数据
 * @param mixed $default 默认值
 * @param bool $sanitize 是否清理数据
 * @return mixed
 */
function input(?string $key = null, $default = null, bool $sanitize = true)
{
    $data = $_REQUEST;

    if ($key === null) {
        return $sanitize ? array_map('sanitize_input', $data) : $data;
    }

    if (!isset($data[$key])) {
        return $default;
    }

    $value = $data[$key];

    return $sanitize ? sanitize_input($value) : $value;
}

/**
 * 获取 POST 数据
 *
 * @param string|null $key 字段名
 * @param mixed $default 默认值
 * @param bool $sanitize 是否清理数据
 * @return mixed
 */
function post(?string $key = null, $default = null, bool $sanitize = true)
{
    if ($key === null) {
        return $sanitize ? array_map('sanitize_input', $_POST) : $_POST;
    }

    if (!isset($_POST[$key])) {
        return $default;
    }

    $value = $_POST[$key];

    return $sanitize ? sanitize_input($value) : $value;
}

/**
 * 获取 GET 数据
 *
 * @param string|null $key 字段名
 * @param mixed $default 默认值
 * @param bool $sanitize 是否清理数据
 * @return mixed
 */
function get(?string $key = null, $default = null, bool $sanitize = true)
{
    if ($key === null) {
        return $sanitize ? array_map('sanitize_input', $_GET) : $_GET;
    }

    if (!isset($_GET[$key])) {
        return $default;
    }

    $value = $_GET[$key];

    return $sanitize ? sanitize_input($value) : $value;
}

/**
 * 清理输入数据
 *
 * @param mixed $value 输入值
 * @return mixed
 */
function sanitize_input($value)
{
    if (is_array($value)) {
        return array_map('sanitize_input', $value);
    }

    if (is_string($value)) {
        return htmlspecialchars(trim($value), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
    }

    return $value;
}

/**
 * 检查当前请求是否为 GET 请求
 */
function is_get(): bool
{
    return ($_SERVER['REQUEST_METHOD'] ?? '') === 'GET';
}

/**
 * 检查当前请求是否为 POST 请求
 */
function is_post(): bool
{
    return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
}

/**
 * 检查是否为 AJAX 请求
 */
function is_ajax(): bool
{
    return !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
        && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}

/**
 * 检测是否使用手机访问
 */
function is_mobile(): bool
{
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

    // 常见移动设备标识
    $mobileKeywords = [
        'mobile', 'android', 'iphone', 'ipod', 'blackberry',
        'windows phone', 'symbian', 'palm', 'opera mini'
    ];

    foreach ($mobileKeywords as $keyword) {
        if (stripos($userAgent, $keyword) !== false) {
            return true;
        }
    }

    return false;
}

/**
 * 获取客户端 IP 地址
 */
function get_ip(): string
{
    static $ip = null;

    if ($ip !== null) {
        return $ip;
    }

    // 按优先级检查不同的 IP 来源
    $headers = [
        'HTTP_X_REAL_IP',
        'HTTP_X_FORWARDED_FOR',
        'HTTP_CLIENT_IP',
        'REMOTE_ADDR',
    ];

    foreach ($headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ips = explode(',', $_SERVER[$header]);
            $ip = trim($ips[0]);

            // 验证 IP 有效性
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
    }

    return $ip = '0.0.0.0';
}

/**
 * ============================================================================
 * 会话和闪存函数
 * ============================================================================
 */

/**
 * 确保会话已启动
 */
function ensure_session_started(): void
{
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
}

/**
 * 获取会话值
 *
 * @param string|null $key 会话键名，null 返回所有会话数据
 * @param mixed $default 默认值
 * @return mixed
 */
function session(?string $key = null, $default = null)
{
    ensure_session_started();

    if ($key === null) {
        return $_SESSION;
    }

    return $_SESSION[$key] ?? $default;
}

/**
 * 设置会话值
 *
 * @param string|array $key 键名或键值对数组
 * @param mixed $value 值
 * @return void
 */
function session_set($key, $value = null): void
{
    ensure_session_started();

    if (is_array($key)) {
        foreach ($key as $k => $v) {
            $_SESSION[$k] = $v;
        }
        return;
    }

    $_SESSION[$key] = $value;
}

/**
 * 删除会话值
 *
 * @param string|array $keys 要删除的键名或键名数组
 * @return void
 */
function session_forget($keys): void
{
    ensure_session_started();

    $keys = (array) $keys;

    foreach ($keys as $key) {
        unset($_SESSION[$key]);
    }
}

/**
 * 清空所有会话数据
 */
function session_flush(): void
{
    ensure_session_started();
    $_SESSION = [];
}

/**
 * 获取旧输入值（用于表单回填）
 *
 * @param string $key 字段名
 * @param mixed $default 默认值
 * @return mixed
 */
function old(string $key, $default = '')
{
    ensure_session_started();

    $value = $_SESSION['_old_input'][$key] ?? $default;

    // 获取后清除（避免重复使用）
    unset($_SESSION['_old_input'][$key]);

    return $value;
}

/**
 * 存储旧输入数据
 *
 * @param array $data 表单数据
 * @return void
 */
function flash_input(array $data): void
{
    ensure_session_started();
    $_SESSION['_old_input'] = $data;
}

/**
 * ============================================================================
 * 环境和配置函数
 * ============================================================================
 */

/**
 * 获取环境变量
 *
 * @param string $key 环境变量键名
 * @param mixed $default 默认值
 * @return mixed
 */
function env(string $key, $default = null)
{
    static $env = null;

    // 懒加载 .env 文件
    if ($env === null) {
        $envFile = defined('ROOT_PATH') ? ROOT_PATH . '.env' : __DIR__ . '/../.env';

        if (file_exists($envFile)) {
            $env = parse_ini_file($envFile, false, INI_SCANNER_RAW);
        } else {
            $env = [];
        }
    }

    // 优先从系统环境变量获取
    $value = getenv($key);
    if ($value !== false) {
        return parse_env_value($value);
    }

    // 从 .env 文件获取
    if (isset($env[$key])) {
        return parse_env_value($env[$key]);
    }

    return $default;
}

/**
 * 解析环境变量值
 *
 * @param string $value 原始值
 * @return mixed
 */
function parse_env_value(string $value)
{
    $value = trim($value);

    // 布尔值
    if (in_array(strtolower($value), ['true', 'false'], true)) {
        return strtolower($value) === 'true';
    }

    // null
    if (strtolower($value) === 'null') {
        return null;
    }

    // 数字
    if (is_numeric($value)) {
        return strpos($value, '.') !== false ? (float) $value : (int) $value;
    }

    // 去除引号
    if (preg_match('/^(["\'])(.*)\1$/', $value, $matches)) {
        return $matches[2];
    }

    return $value;
}

/**
 * ============================================================================
 * URL 和路由函数
 * ============================================================================
 */

/**
 * 生成完整的 URL
 *
 * @param string|null $path 路径
 * @return string
 */
function url(?string $path = null): string
{
    $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';

    // 验证主机名
    if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
        throw new InvalidArgumentException('无效的主机名');
    }

    // 返回当前完整 URL
    if ($path === null || $path === '') {
        return $protocol . '://' . $host . ($_SERVER['REQUEST_URI'] ?? '');
    }

    // 确保路径以 / 开头
    $path = '/' . ltrim($path, '/');

    return $protocol . '://' . $host . $path;
}

/**
 * 生成资源 URL
 *
 * @param string $path 资源路径
 * @return string
 */
function asset(string $path): string
{
    $baseUrl = rtrim(config('app.asset_url', ''), '/');
    $path = '/' . ltrim($path, '/');

    return $baseUrl . $path;
}

/**
 * ============================================================================
 * 安全函数
 * ============================================================================
 */

/**
 * HTML 转义
 *
 * @param string $value 要转义的字符串
 * @return string
 */
function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}

/**
 * 生成 CSRF Token
 *
 * @return string
 */
function csrf_token(): string
{
    ensure_session_started();

    if (!isset($_SESSION['_csrf_token'])) {
        $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
    }

    return $_SESSION['_csrf_token'];
}

/**
 * 验证 CSRF Token
 *
 * @param string $token 要验证的 token
 * @return bool
 */
function csrf_verify(string $token): bool
{
    ensure_session_started();

    if (!isset($_SESSION['_csrf_token'])) {
        return false;
    }

    return hash_equals($_SESSION['_csrf_token'], $token);
}

/**
 * 生成 CSRF 隐藏字段
 *
 * @return string
 */
function csrf_field(): string
{
    return '<input type="hidden" name="_token" value="' . csrf_token() . '">';
}

/**
 * XSS 过滤
 *
 * @param string $string 输入字符串
 * @return string
 */
function clean_xss(string $string): string
{
    // 移除不可见字符
    $string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);

    // 移除危险标签和属性
    $string = strip_tags($string);

    // HTML 实体编码
    return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}

/**
 * ============================================================================
 * 字符串和格式化函数
 * ============================================================================
 */

/**
 * 生成唯一 ID
 *
 * @param int $length ID 长度
 * @return string
 */
function uuid(int $length = 32): string
{
    $bytes = random_bytes((int) ceil($length / 2));
    return substr(bin2hex($bytes), 0, $length);
}

/**
 * 友好时间显示
 *
 * @param int $timestamp 时间戳
 * @return string
 */
function time_ago(int $timestamp): string
{
    $diff = time() - $timestamp;

    if ($diff < 60) {
        return '刚刚';
    }

    $units = [
        31536000 => '年前',
        2592000  => '个月前',
        604800   => '周前',
        86400    => '天前',
        3600     => '小时前',
        60       => '分钟前',
    ];

    foreach ($units as $seconds => $label) {
        if ($diff >= $seconds) {
            $count = floor($diff / $seconds);
            return $count . $label;
        }
    }

    return '刚刚';
}

/**
 * 截取字符串
 *
 * @param string $string 原字符串
 * @param int $length 截取长度
 * @param string $suffix 后缀
 * @return string
 */
function str_limit(string $string, int $length = 100, string $suffix = '...'): string
{
    if (mb_strlen($string, 'UTF-8') <= $length) {
        return $string;
    }

    return mb_substr($string, 0, $length, 'UTF-8') . $suffix;
}

/**
 * ============================================================================
 * 调试函数
 * ============================================================================
 */

/**
 * 调试输出
 *
 * @param mixed ...$values 要输出的值
 * @return void
 */
function dump(...$values): void
{
    echo '<pre style="background:#f5f5f5;padding:10px;border:1px solid #ddd;border-radius:4px;">';

    foreach ($values as $value) {
        var_dump($value);
    }

    echo '</pre>';
}

/**
 * 调试输出并终止
 *
 * @param mixed ...$values 要输出的值
 * @return void
 */
function dd(...$values): void
{
    dump(...$values);
    exit(1);
}

/**
 * 记录日志
 *
 * @param string $message 日志消息
 * @param string $level 日志级别
 * @return void
 */
function logger(string $message, string $level = 'info'): void
{
    $logFile = defined('LOG_PATH') ? LOG_PATH . 'app.log' : sys_get_temp_dir() . '/app.log';
    $timestamp = date('Y-m-d H:i:s');
    $log = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;

    file_put_contents($logFile, $log, FILE_APPEND | LOCK_EX);
}
